
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>Jenkins优化Kubernetes部署流水线</title>
            </head>
            <body>
            <a href="https://andyoung.blog.csdn.net">原作者博客</a>
            <div id="content_views" class="markdown_views prism-atom-one-light">
                    <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                        <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                    </svg>
                    <blockquote> 
 <p>由于jenkins 版本升级导致以前的功能不能用了。主要是 kubernetes-plugin 配置文件位置换了。导致以前的配置文件丢失，在实践中又发现比以前更好的实现方式。故重新写个新版教程。<br> 该篇文件需要配合 <a href="https://andyoung.blog.csdn.net/article/details/121851209" rel="nofollow">基于K8S构建企业级Jenkins CI/CD平台实战（二） 之 kubernetes-plugin 插件使用</a> 观看</p> 
</blockquote> 
<h2><a id="jenkins__3"></a>jenkins 安装</h2> 
<p>参考 <a href="https://andyoung.blog.csdn.net/article/details/121850743" rel="nofollow">基于K8S构建企业级Jenkins CI/CD平台实战（一） 之 环境搭建</a></p> 
<h2><a id="JenkinsAgent_7"></a>Jenkins定制Agent</h2> 
<blockquote> 
 <p>可以使用我个人已经编译好的 <code>docker pull andanyoung/kubectl-maven-agent</code></p> 
</blockquote> 
<p>由于官方提供了简单的 jenkins-agent。 如果我们的打包流程需要用到git、Java、Maven、Kubernetes的话，我们也需要将这些集成到行的 jenkins-agent 中。 我们基于Jenkins的官方基础镜像进行定制，Dockerfile如下：</p> 
<pre><code>FROM jenkins/inbound-agent:latest-jdk8

ARG MAVEN_VERSION=3.6.3
ARG KUBECTL_VERSION=v1.22.3

# tool
USER root
RUN apt-get update &amp;&amp; \
  apt-get install -y curl &amp;&amp; \
  apt-get clean

# maven
RUN curl -OL https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz &amp;&amp; \
  tar -zxf ./apache-maven-${MAVEN_VERSION}-bin.tar.gz &amp;&amp; \
  mv apache-maven-${MAVEN_VERSION} /usr/local &amp;&amp; \
  rm -f apache-maven-${MAVEN_VERSION}-bin.tar.gz &amp;&amp; \
  ln -s /usr/local/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/bin/mvn &amp;&amp; \
  ln -s /usr/local/apache-maven-${MAVEN_VERSION} /usr/local/apache-maven &amp;&amp; \
  mkdir -p /home/jenkins/.m2  &amp;&amp; \
  chown -R jenkins:jenkins  /home/jenkins/.m2

# kubectl
RUN curl -OL https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl &amp;&amp; \
  chmod +x ./kubectl &amp;&amp; \
  mv ./kubectl /usr/local/bin/kubectl &amp;&amp; \
  ln -snf /usr/share/zoneinfo/$TZ /etc/localtime &amp;&amp; \
  echo $TZ &gt; /etc/timezone 

VOLUME [ "/home/jenkins/.m2" ]

USER jenkins

ENTRYPOINT ["/usr/local/bin/jenkins-agent"]

# docker build --build-arg KUBECTL_VERSION=v1.22.3 --build-arg MAVEN_VERSION=3.6.3 -t "andanyoung/kubectl-maven-agent" .
# docker push andanyoung/kubectl-maven-agent
</code></pre> 
<h4><a id="inboundagent____nlpslave__52"></a>inbound-agent 和 nlp-slave 的区别</h4> 
<p>Jenkins agent 官方 Docker 镜像重命名了 。</p> 
<blockquote> 
 <ul><li>jenkins/agent 镜像：原名称为jenkins/slave，从4.3-2 开始更名为</li><li>jenkins/agentjenkins/inbound-agent 镜像：原名称为 jenkins/jnlp-slave ，从 4.3-2 开始更名为 jenkins/inbound-agent</li><li>jenkins/ssh-agent 镜像：原名称为 jenkins/ssh-slave ，从 2.0.0 开始更名为 jenkins/ssh-agent</li></ul> 
</blockquote> 
<h2><a id="kubernetesplugin__60"></a>kubernetes-plugin 的使用</h2> 
<p>参考 <a href="https://andyoung.blog.csdn.net/article/details/121851209" rel="nofollow">基于K8S构建企业级Jenkins CI/CD平台实战（二） 之 kubernetes-plugin 插件使用</a></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/e745a6edbcbd1a5fb27ed2cea7deeb5b.png" alt=""></p> 
<h3><a id="jenkinsK8S_66"></a>jenkins插件连接K8S配置</h3> 
<p>需要先安装pipeline插件, 建议不在 pipeline UI上配置pod创建模版，免得以后每来一个项目都要创建，管理不方便，建议使用pipeline统一 配置。<br> Kubernetes插件介绍:https://github.com/jenkinsci/kubernetes-plugin<br> 配置路径 “系统管理” —&gt; “系统设置” —&gt; “云（翻到最后）” —&gt; “Kubernetes”<br> 新版已放到 “节点管理” -&gt; “configureClouds” (&gt; v2.343)</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/0d7bc3f71be50b3473f4e491359751cb.png" alt=""></p> 
<blockquote> 
 <ul><li>Name 处默认为 kubernetes，也可以修改为其他名称，如果这里修改了，下边在执行 Job 时指定 podTemplate() 参数 cloud 为其对应名称，否则会找不到，cloud 默认值取：kubernetes</li><li>Kubernetes URL jenkins找到 k8s的地址。 我填写了 https://kubernetes.default 这里我填写了 Kubernetes Service 对应的 DNS 记录，通过该 DNS 记录可以解析成该 Service 的 Cluster IP，注意：也可以填写 https://kubernetes.default.svc.cluster.local 完整 DNS 记录，因为它要符合 &lt;svc_name&gt;.&lt;namespace_name&gt;.svc.cluster.local 的命名方式，或者直接填写外部 Kubernetes 的地址 https://:。</li><li>Jenkins URL 是k8s 访问jenkins地址。这里我填写了 http://jenkins.default / http://jenkins.ops （这里是jenkins service 地址带上了命名空间）。跟上边类似，也是使用 Jenkins Service 对应的 DNS 记录，不过要指定为 8080 端口，因为我们设置暴漏 8080 端口。同时也可以用 http://:&lt;Node_Port&gt; 方式，例如我这里可以填 http://192.168.99.100:30645 也是没有问题的，这里的 30645 就是对外暴漏的 NodePort。<br> 注意 命名空间</li><li>Kubernetes 命名空间 可不写</li></ul> 
</blockquote> 
<h3><a id="_83"></a>测试并验证</h3> 
<p>通过 Kubernetes 安装 Jenkins Master 完毕并且已经配置好了连接，接下来，我们可以配置 Job 测试一下是否会根据配置的 Label 动态创建一个运行在 Docker Container 中的 Jenkins Slave 并注册到 Master 上，而且运行完 Job 后，Slave 会被注销并且 Docker Container 也会自动删除吧！</p> 
<blockquote> 
 <p>创建一个 Pipeline 类型 Job 并命名为 my-k8s-jenkins-pipeline，然后在 Pipeline 脚本处填写一个简单的测试脚本如下：</p> 
</blockquote> 
<pre><code>def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, cloud: 'kubernetes')
{
    node(label) {
        stage('Run shell') {
            sh 'sleep 30s'
            sh 'echo hello world.'
        }
    }
}

</code></pre> 
<blockquote> 
 <p>node(label ) 中的 label ：匹配 node 配置 为 label的节点。</p> 
 <p>podTemplate(label） 中的 label: 定义 节点 node 运行 podTemplate 的 label ，也就是 节点node的 label</p> 
 <p>可以看出 podTemplate 和node 配置并没有相关，只需要label 相关联</p> 
</blockquote> 
<h3><a id="_kubernetes_pod_tempalte_113"></a>创建 kubernetes pod tempalte</h3> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/6bf5d4e5c2a2c74a4ee72dfa01d37a6d.png" alt="请添加图片描述"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/0ac2e3e0edebed27e24be48b40cf288b.png" alt="请添加图片描述"><br> <img src="https://i-blog.csdnimg.cn/blog_migrate/ac40b89b8cbdb7e44441375215e4ca56.png" alt="请添加图片描述"></p> 
<h4><a id="docker_121"></a>运行docker命令</h4> 
<p>在Kubernetes集群内部，Kubernetes映射/var/run/docker.sock（因此我们共享相同的Docker sock）。 将jenkins用户添加到默认docker用户组下, 从而保证jenkins可以直接访问/var/run/docker.sock</p> 
<blockquote> 
 <p>usermod -a -G docker jenkins</p> 
</blockquote> 
<h3><a id="_kubernetes_127"></a>配置 kubernetes凭证</h3> 
<p>参考<a href="https://andyoung.blog.csdn.net/article/details/121809583" rel="nofollow">k8s 带你一步步 创建用户账号(User Account)</a>, 最终生成一个用户账号 和一个kube配置文件</p> 
<p>#<img src="https://i-blog.csdnimg.cn/blog_migrate/394d08647410372d6b4115293a978acc.png" alt="请添加图片描述"><img src="https://i-blog.csdnimg.cn/blog_migrate/9f0b5efac407786e51a361d2600932c6.png" alt="请添加图片描述"></p> 
<h3><a id="Pipeline_134"></a>测试Pipeline</h3> 
<pre><code>podTemplate (inheritFrom: "maven-kubectl"){
    node(POD_LABEL) {
            stage('Run shell') {
                sh 'echo hello world'
                sh 'kubectl config view'
                sh 'docker version'
            }
            
            stage('k8s') { 
                
                kubeconfig(credentialsId: '586b9f76-15d3-4d35-8c76-28e02b144ce9') {
                    sh 'kubectl config view'
                    sh "kubectl get pod -n prod"
                }
        }
    }
}
</code></pre> 
<blockquote> 
 <p>采用配置文件的方式就不用写 serverUrl 了。因为配置文件就包含serverUrl</p> 
</blockquote> 
<h3><a id="SpringBoot_158"></a>构建SpringBoot脚本</h3> 
<ul><li>1.先在k8s里创建 deployment。yml如下</li></ul> 
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: ces-purchase
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pod-purchase
  template:
    metadata:
      labels:
        app: pod-purchase
    spec:
      affinity:
        # 反亲和性调度 使各个pod不在同个node里
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                topologyKey: kubernetes.io/hostname
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - pod-purchase
      imagePullSecrets:
        - name: harborregcred
      containers:
        - name: pod-ces-purchase
          image: docker.aliuncle.top:8000/ces/ces-purchase-impl:1.1-SNAPSHOT
          imagePullPolicy: Always
          resources:
            limits:
              memory: 2G
            requests:
              memory: 2G
          lifecycle:
            postStart:
              exec:
                command:
                  - sleep
                  - 30s
            preStop:
              exec:
                command:
                  - sh
                  - '-c'
                  - &gt;-
                    curl -X "POST"
                    "http://localhost:8080/actuator/serviceregistry?status=DOWN"
                    -H "Content-Type:
                    application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
                  - sleep 30s
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 60
            timeoutSeconds: 10
            periodSeconds: 120
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 10
            timeoutSeconds: 10
            periodSeconds: 120
          env:
            - name: SW_AGENT_NAME
              value: ces-purchase
            - name: SW_AGENT_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
              valueFrom:
                configMapKeyRef:
                  name: skywalking-cm
                  key: skywalking.collector.backend_service

</code></pre> 
<ul><li>2.jenkins 只负责拉代码，打包，调用k8s更新方法</li></ul> 
<pre><code>def k8s_deployment = project_name
//def run_env = "pre"
def app_name = project_name
//项目git 地址
def git_address = "http://git.***.com/ces/ces-application/${project_name}.git"
//项目git 分支地址
def git_branch = "prod"
//git 证书ID
def git_auth = "andnayang-git"  

def docker_path = run_env == "pre" ? "ces_pre":"ces"
//打包镜像名称
def docker_image_name = "docker.**.top:8000/${docker_path}/${app_name}-impl"
//打包镜像tag
def docker_image_tag = BUILD_NUMBER
//docker registry 地址
def docker_registry = "docker.***.top:8000" 
def docker_registry_auth = "a83fa85e-5753-484b-9076-79baa2d6d437"   

def docker_host = "tcp://192.168.0.192:2375"
def APM_OPT = '${APM_OPT}'

podTemplate (inheritFrom: "maven-kubectl"){
  node(POD_LABEL){
      // 第一步
      stage('拉取代码'){
         git branch: "${git_branch}", credentialsId: "${git_auth}", url: "${git_address}"
      }
      // 第二步
      stage('代码编译'){
          sh "mvn clean package -Dmaven.test.skip=true -P${run_env}"
      }
      // 第三步
      stage('构建镜像'){
          //Harbor镜像仓库登录验证，
          withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
            sh """
              echo '
                FROM docker.***.top:8000/common/jdk8

                MAINTAINER ***@qq.com
                
                ENTRYPOINT /usr/bin/java -Djava.security.egd=file:/dev/./urandom -javaagent:/root/agent/skywalking-agent.jar ${APM_OPT} -jar app.jar
                
                COPY ${app_name}-impl/target/${app_name}-impl.jar app.jar
              ' &gt; Dockerfile
              export DOCKER_HOST="${docker_host}"
              docker login -u ${username} -p ${password} ${docker_registry}
              docker build -t ${docker_image_name}:${docker_image_tag} .
              docker push ${docker_image_name}:${docker_image_tag}
            """
            }
      }
      // 第四步
  
    stage('部署到K8S平台'){
        kubeconfig(credentialsId: '586b9f76-15d3-4d35-8c76-28e02b144ce9') {
                    // sh "kubectl rollout restart -n ${run_env} deployment ${k8s_deployment}"
                     sh "kubectl set image deployments/${app_name} pod-${app_name}=${docker_image_name}:${docker_image_tag} -n ${run_env}"
        }
             
            //sh 'kubectl config view'
            //sh "kubectl set image deployments/hello-k8s pod-hello-k8s=andanyoung/springboot-hello:${docker_image_tag} -n pre"
    }
        
  }
}

</code></pre> 
<h2><a id="jenkins__324"></a>上游jenkins 并发构建</h2> 
<ul><li>配置</li></ul> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/e52bf6ed02fd43f3a47b6bc4fcb3bd77.png" alt="请添加图片描述"><br> <img src="https://i-blog.csdnimg.cn/blog_migrate/1369cd9fff875c91724ff4f60da4d933.png" alt="请添加图片描述"></p> 
<ul><li>pipeline</li></ul> 
<pre><code>pipeline {
     agent any 
     stages {
         stage('deploy stage') {
             steps {
                 script{
                    //把选择的项目信息转为数组
                    def selectedProjects = "${project_name}".split(',')
                    for (job in selectedProjects){
                        echo "${job} 正在构建发布"
                        build wait: false, propagate: false, job: 'ces-application', parameters: [string(name: 'project_name', value: "${job}"), string(name: 'run_env', value: 'prod')]
                       // build wait: false, propagate: false,  job: "${job}"
                        // 传入gitParameter参数  $mbranch为本次pipeline流水线执行时传入的参数，然后将参数值 传入给每个执行构建任务中
                        echo "${job} 完成构建发布."
                    }
                 }
             }
         }
     }
}

</code></pre> 
<ul><li>效果<br> <img src="https://i-blog.csdnimg.cn/blog_migrate/8f1fc879a5ab514a561922e0a95e6939.png" alt="请添加图片描述"></li></ul> 
<h2><a id="_360"></a>参考</h2> 
<ul><li>http://idcsec.com/2019/03/27/%E7%94%A8jenkins%E5%8A%A8%E6%80%81%E4%BC%B8%E7%BC%A9%E5%92%8Ckubernetes%E4%B8%BA%E5%BA%94%E7%94%A8%E8%AE%BE%E7%BD%AEci-cd-%E6%B5%81%E7%A8%8B/</li><li>https://www.coder4.com/homs_online/ch06-cd/jenkins-k8s-optimize.html</li></ul>
                </div>
            </body>
            </html>
            